/*
 * dex2jar - Tools to work with android .dex and java .class files
 * Copyright (c) 2009-2012 Panxiaobo
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.dex2jar.v3;

import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.FileUtils;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.MethodNode;

import com.googlecode.dex2jar.Method;
import com.googlecode.dex2jar.ir.Constant;
import com.googlecode.dex2jar.ir.ET;
import com.googlecode.dex2jar.ir.IrMethod;
import com.googlecode.dex2jar.ir.Value;
import com.googlecode.dex2jar.ir.expr.Exprs;
import com.googlecode.dex2jar.ir.stmt.Stmts;
import com.googlecode.dex2jar.reader.DexFileReader;
import com.googlecode.dex2jar.util.ASMifierCodeV;
import com.googlecode.dex2jar.util.Escape;
import com.googlecode.dex2jar.util.Out;
import com.googlecode.dex2jar.visitors.DexClassVisitor;
import com.googlecode.dex2jar.visitors.DexCodeVisitor;
import com.googlecode.dex2jar.visitors.DexMethodVisitor;
import com.googlecode.dex2jar.visitors.EmptyVisitor;

public class DexExceptionHandlerImpl implements DexExceptionHandler {

    private int readerConfig = DexFileReader.SKIP_DEBUG;

    private Map<Method, Exception> exceptions = new HashMap<Method, Exception>();

    public Map<Method, Exception> getExceptions() {
        return exceptions;
    }

    @Override
    public void handleFileException(Exception e) {
        e.printStackTrace(System.err);
    }

    @Override
    public void handleMethodTranslateException(Method method, IrMethod irMethod, MethodNode methodNode, Exception e) {
        this.exceptions.put(method, e);// record the exception

        // replace the generated code with
        // 'return new RuntimeException("Generated by Dex2jar, and Some Exception Caught : xxxxxxxxxxxxx");'
        StringWriter s = new StringWriter();
        e.printStackTrace(new PrintWriter(s));
        String msg = s.toString();
        methodNode.instructions.clear();
        methodNode.tryCatchBlocks.clear();
        irMethod.traps.clear();
        irMethod.stmts.clear();
        irMethod.stmts.add(Stmts.nThrow(Exprs.nInvokeNew(
                new Value[] { Constant.nString("Generated by Dex2jar, and Some Exception Caught :" + msg), },
                new Type[] { Type.getType(String.class) }, Type.getType(RuntimeException.class))));
        new IrMethod2AsmMethod().convert(irMethod, methodNode);
    }

    public static String getVersionString() {
        return "version: reader-" + DexFileReader.class.getPackage().getImplementationVersion() + ", translator-"
                + Main.class.getPackage().getImplementationVersion() + ", ir-"
                + ET.class.getPackage().getImplementationVersion();
    }

    public DexExceptionHandlerImpl skipDebug(boolean b) {
        if (b) {
            this.readerConfig |= DexFileReader.SKIP_DEBUG;
        } else {
            this.readerConfig &= ~DexFileReader.SKIP_DEBUG;
        }
        return this;
    }

    public DexExceptionHandlerImpl skipDebug() {
        this.readerConfig |= DexFileReader.SKIP_DEBUG;
        return this;
    }

    public void dumpException(DexFileReader reader, File errorFile) throws IOException {
        for (Map.Entry<Method, Exception> e : exceptions.entrySet()) {
            System.err.println("Error:" + e.getKey().toString() + "->" + e.getValue().getMessage());
        }

        final ZipOutputStream errorZipOutputStream = new ZipOutputStream(FileUtils.openOutputStream(errorFile));
        errorZipOutputStream.putNextEntry(new ZipEntry("summary.txt"));
        final PrintWriter fw = new PrintWriter(new OutputStreamWriter(errorZipOutputStream, "UTF-8"));
        fw.println(getVersionString());
        fw.println("there are " + exceptions.size() + " error methods");
        fw.print("options: ");
        if((readerConfig & DexFileReader.SKIP_DEBUG) == 0){
            fw.print(" -d");
        }
        fw.println();
        fw.flush();
        errorZipOutputStream.closeEntry();
        final Out out = new Out() {

            @Override
            public void pop() {

            }

            @Override
            public void push() {

            }

            @Override
            public void s(String s) {
                fw.println(s);
            }

            @Override
            public void s(String format, Object... arg) {
                fw.println(String.format(format, arg));
            }
        };
        final int[] count = new int[] { 0 };
        reader.accept(new EmptyVisitor() {

            @Override
            public DexClassVisitor visit(int accessFlags, String className, String superClass, String[] interfaceNames) {
                return new EmptyVisitor() {

                    @Override
                    public DexMethodVisitor visitMethod(final int accessFlags, final Method method) {
                        if (exceptions.containsKey(method)) {
                            return new EmptyVisitor() {

                                @Override
                                public DexCodeVisitor visitCode() {
                                    try {
                                        errorZipOutputStream.putNextEntry(new ZipEntry("t" + count[0]++ + ".txt"));
                                    } catch (IOException e) {
                                        throw new RuntimeException(e);
                                    }
                                    Exception exception = exceptions.get(method);
                                    exception.printStackTrace(fw);
                                    out.s("");
                                    out.s("DexMethodVisitor mv=cv.visitMethod(%s, %s);", Escape.methodAcc(accessFlags),
                                            Escape.v(method));
                                    out.s("DexCodeVisitor code = mv.visitCode();");
                                    return new ASMifierCodeV(out);
                                }

                                @Override
                                public void visitEnd() {
                                    out.s("mv.visitEnd();");
                                    fw.flush();
                                    try {
                                        errorZipOutputStream.closeEntry();
                                    } catch (IOException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                            };
                        }
                        return null;
                    }

                };
            }

        }, readerConfig);
        errorZipOutputStream.close();

    }

}